﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using FeatureDetection.Inputter;
using System.Collections;
using System.Windows.Media.Media3D;
using System.Collections.Generic;
using System.Numerics;

namespace FeatureDetection {

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged {

        #region INotifyPropertyChanged メンバー

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        class DelegateCommand : ICommand {
            public Action<object> ExecuteHandler { get; set; }
            public Func<object, bool> CanExecuteHandler { get; set; }

            #region ICommand メンバー
            public bool CanExecute(object parameter) {
                var d = CanExecuteHandler;
                return d == null ? true : d(parameter);
            }

            public event EventHandler CanExecuteChanged;

            public void Execute(object parameter) {
                var d = ExecuteHandler;
                if(d != null)
                    d(parameter);
            }

            public void RaiseCanExecuteChanged() {
                var d = CanExecuteChanged;
                if(d != null)
                    d(this, null);
            }
            #endregion
        }
        #region MainWindow フィールド
        private IInputter inputter { get; set; } //入力方法別入力管理クラス
        private DrawingGroup drawingGroup { get; set; }  //イメージコントローラ
        private Drawer drawer { get; set; }   //ビューア描画クラス
        #endregion

        #region MainWindow プロパティ
        public enum InputType {
            MouseInput,
            KinectInput,
            FileInput
        }
        private InputType _inputType;
        /// <summary>
        /// 入力方法を設定または取得する
        /// </summary>
        public InputType inputType {
            get { return _inputType; }
            set {
                _inputType = value;
                PropertyChanged(this, new PropertyChangedEventArgs("inputType"));
            }
        }

        private DrawingImage _imageSource;
        /// <summary>
        /// ビューア用画像ソースを取得する
        /// </summary>
        public DrawingImage imageSource {
            get { return _imageSource; }
            private set { 
                _imageSource = value;
                PropertyChanged(this, new PropertyChangedEventArgs("imageSource"));    
            }
        }

        /// <summary>
        /// 取得座標リストを設定または取得する
        /// </summary>
        public ObservableCollection<FeatureDetection.FeatureDetector.Trajection_Part> positions { get; set; }
        public ObservableCollection<FeatureDetection.FeatureDetector.Trajection_Part> features { get; set; }

        private double _startFrame;
        public double startFrame {
            get { return _startFrame; }
            set {
                _startFrame = value;
                PropertyChanged(this, new PropertyChangedEventArgs("startFrame"));
            }
        }

        private double _endFrame;
        public double endFrame {
            get { return _endFrame; }
            set {
                _endFrame = value;
                PropertyChanged(this, new PropertyChangedEventArgs("endFrame"));
            }
        }


        private string _buttonInputText;
        /// <summary>
        /// 取り込みボタンのテキストを取得する
        /// </summary>
        public string buttonInputText {
            get { return _buttonInputText; }
            private set { 
                _buttonInputText = value;
                PropertyChanged(this, new PropertyChangedEventArgs("buttonInputText"));
            }
        }
        
        #endregion
        
        #region MainWindow コマンドプロパティ
        private ICommand _okInput;
        /// <summary>
        /// 取り込みボタンのイベントを取得する
        /// </summary>
        public ICommand OkInput {
            get { return _okInput; }
            private set { 
                _okInput = value;
                PropertyChanged(this, new PropertyChangedEventArgs("OkInput"));
            }
        }
        /// <summary>
        /// 取り込みボタンが押された
        /// </summary>
        private void OkInputExecute(object parameter) {
            //Inputterのセット
            switch(this.inputType) {
                case InputType.MouseInput:
                    this.inputter = new MouseInputter(this.Image, 60); break;
                case InputType.KinectInput:
                    this.inputter = new KinectInputter(); break;
                case InputType.FileInput:
                    this.inputter = new FileInputter(); break;
                default:
                    break;
            }

            if(inputter == null) return;

            //Inputterの初期化
            if(!inputter.initialize()) {
                System.Windows.Forms.MessageBox.Show(inputter + "の初期化に失敗しました");
                return;
            }

            //古い軌跡情報を破棄
            this.positions.Clear();
            this.startFrame = double.NaN;
            this.endFrame = double.NaN;

            //Inputterの起動
            this.SetOkInput_Abort();
            inputter.startInput(
                item => {
                    this.positions.Add(new FeatureDetection.FeatureDetector.Trajection_Part(positions.Count + 1, item));
                    this.drawer.Invalidate();
                },
                () => this.OkAbortExecute(null));

        }
        /// <summary>
        /// 中止ボタンが押された
        /// </summary>
        private void OkAbortExecute(object parameter) {
            if(inputter != null) this.inputter.endInput();
            this.inputter = null;
            this.startFrame = 1;
            this.endFrame = this.positions.Count;
            this.SetOkInput_Input();

            var ret = new Collection<FeatureDetection.FeatureDetector.Trajection_Part>();

            System.Windows.Forms.MessageBox.Show("軌跡を再配置します");

            //始点と終点の中点
            double mx = (positions.ElementAt(0).X + positions.ElementAt(positions.Count - 1).X) / 2;
            double my = (positions.ElementAt(0).Y + positions.ElementAt(positions.Count - 1).Y) / 2;
            double mz = (positions.ElementAt(0).Z + positions.ElementAt(positions.Count - 1).Z) / 2;
            foreach(var item in positions) {
                ret.Add(new FeatureDetector.Trajection_Part(item.Num, new Point3D(item.X - mx, item.Y - my, item.Y - mz)));
            }

            //描画
            Dispatcher.BeginInvoke((Action)(() => {
                this.positions.Clear();
                foreach(var item in ret) {
                    this.positions.Add(item);
                }
                this.drawer.Invalidate();
            }), System.Windows.Threading.DispatcherPriority.Normal);

            System.Windows.Forms.MessageBox.Show("再配置完了");

            if(System.Windows.Forms.MessageBox.Show("正規化しますか？", "正規化", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes) {

                //軌跡の総距離
                var pos = new List<Point3D>();
                foreach(var item in ret) pos.Add(new Point3D(item.X, item.Y, item.Z));
                ret.Clear();
                double totalLength = 0;
                var p0 = pos[0];
                for(int i = 1; i < pos.Count; ++i) {
                    totalLength += (pos[i] - p0).Length;
                    p0 = pos[i];
                }

                //軌跡の等間隔化
                const int div = 200;    //分割数
                int j = 0;  //現在の始点
                int k = 0;  //現在のインデックス
                double l = 0;   //残り進行距離

                l = totalLength / div;
                while(j + 1 < pos.Count) {
                    if((pos[j + 1] - pos[j]).Length >= l) {
                        ret.Add(new FeatureDetection.FeatureDetector.Trajection_Part(++k, ((pos[j + 1] - pos[j]) * l / (pos[j + 1] - pos[j]).Length) + pos[j]));
                        var temp = ((pos[j + 1] - pos[j]) * l / (pos[j + 1] - pos[j]).Length) + pos[j];
                        pos[j] = new Point3D(temp.X, temp.Y, temp.Z);
                        l = totalLength / div;
                    } else {
                        l -= (pos[j + 1] - pos[j]).Length;
                        ++j;
                    }
                }

                //描画
                Dispatcher.BeginInvoke((Action)(()=>{
                    this.positions.Clear();
                    foreach(var item in ret) {
                        this.positions.Add(item);
                    }
                    this.drawer.Invalidate();
                }),System.Windows.Threading.DispatcherPriority.Normal);

                System.Windows.Forms.MessageBox.Show("正規化完了");
            }
            if(System.Windows.Forms.MessageBox.Show("R型に変換しますか？", "", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes) {

                FeatureDetection.FeatureDetector.Trajection_Part[] copy = new FeatureDetector.Trajection_Part[ret.Count];
                ret.CopyTo(copy,0);
                ret.Clear();
                foreach(var item in copy) {
                    double _x = item.X;
                    double _y = item.Y;
                    double leng = Math.Sqrt((_x*_x)+(_y*_y));
                    double theta = Math.Atan2(_y, _x) + Math.PI * (item.Num-1) / copy.Count();
                    ret.Add(new FeatureDetector.Trajection_Part(item.Num, new Point3D(leng * Math.Cos(theta), leng * Math.Sin(theta),0)));
                }

                //描画
                Dispatcher.BeginInvoke((Action)(() => {
                    this.positions.Clear();
                    foreach(var item in ret) {
                        this.positions.Add(item);
                    }
                    this.drawer.Invalidate();
                }), System.Windows.Threading.DispatcherPriority.Normal);

                System.Windows.Forms.MessageBox.Show("R型閉曲線への変換完了");

                if(System.Windows.Forms.MessageBox.Show("フーリエ変換しますか？", "", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes) {


                    //フーリエ変換
                    int N = ret.Count;
                    int K = N;
                    Complex[] cx = new Complex[K];
                    Complex[] cy = new Complex[K];

                    for(int k = 0; k < K; ++k) {
                        double d = -2 * Math.PI * k / N;
                        for(int n = 0; n < N; ++n) {
                            double phase = d * n;

                            cx[k] += ret.ElementAt(n).X * Complex.Exp(new Complex(0, phase));
                            cy[k] += ret.ElementAt(n).Y * Complex.Exp(new Complex(0, phase));
                        }

                        cx[k] /= N;
                        cy[k] /= N;
                    }

                    ret.Clear();
                    for(int i = 0; i < N; ++i) {
                        ret.Add(new FeatureDetector.Trajection_Part(i, new Point(cx[i].Real, cy[i].Real)));
                    }

                    //ファイル出力
                    Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
                    dlg.FileName = "*.csv";
                    dlg.DefaultExt = "*.csv";
                    dlg.Filter = "CSVドキュメント(*.csv)|*.csv";
                    dlg.AddExtension = true;
                    if(dlg.ShowDialog() == true) {
                        //適切な入力がされた
                        using(var s = new StreamWriter(dlg.FileName)) {
                            s.WriteLine("index,X,,Y");
                            int line = 0;
                            for(int i = 0; i < K; ++i) {
                                s.WriteLine(line++ + "," + cx[i].Real + "," + cx[i].Imaginary + "," + cy[i].Real + "," + cy[i].Imaginary);
                            }
                        }
                    }

                    //描画
                    Dispatcher.BeginInvoke((Action)(() => {
                        this.positions.Clear();
                        foreach(var item in ret) {
                            this.positions.Add(item);
                        }
                        this.drawer.Invalidate();
                    }), System.Windows.Threading.DispatcherPriority.Normal);

                    System.Windows.Forms.MessageBox.Show("フリーエ変換完了");

                    if(System.Windows.Forms.MessageBox.Show("逆フーリエ変換しますか？", "", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes) {

                        //逆フーリエ変換
                        ret.Clear();
                        for(int n = 0; n < N; ++n) {
                            double d = 2 * Math.PI * n / N;

                            Complex temp = new Complex();

                            for(int k = 0; k < K; ++k) {
                                double phase = d * k;

                                temp += new Complex((cx[k] * Complex.Exp(new Complex(0, phase))).Real, (cy[k] * Complex.Exp(new Complex(0, phase))).Real);
                            }
                            //元通りに回転

                            temp *= Complex.Exp(new Complex(0, -Math.PI * n / N));

                            ret.Add(new FeatureDetection.FeatureDetector.Trajection_Part(n + 1, temp.Real, temp.Imaginary));
                        }

                        //描画
                        Dispatcher.BeginInvoke((Action)(() => {
                            this.positions.Clear();
                            foreach(var item in ret) {
                                this.positions.Add(item);
                            }
                            this.drawer.Invalidate();
                        }), System.Windows.Threading.DispatcherPriority.Normal);

                        System.Windows.Forms.MessageBox.Show("完了");
                    }
                }

            }

        }
        /// <summary>
        /// 取り込みボタンが有効である条件
        /// </summary>
        private bool OkInputCanExecute(object parameter) { return true; }
        /// <summary>
        /// 中止ボタンが有効である条件
        /// </summary>
        private bool OkAbortCanExecute(object parameter) { return true; }
        /// <summary>
        /// 取り込みボタンに機能「取り込み」を設定する
        /// </summary>
        private void SetOkInput_Input() {
            this.buttonInputText = "取り込み";
            this.OkInput = new DelegateCommand {
                ExecuteHandler = OkInputExecute,
                CanExecuteHandler = OkInputCanExecute
            };
        }
        /// <summary>
        /// 取り込みボタンに機能「中止」を設定する
        /// </summary>
        private void SetOkInput_Abort() {
            this.buttonInputText = "中止";
            this.OkInput = new DelegateCommand {
                ExecuteHandler = OkAbortExecute,
                CanExecuteHandler = OkAbortCanExecute
            };
        }

        public static readonly ICommand OkSetStartFrame = new RoutedCommand("OkSetStartFrame", typeof(MainWindow));
        private void OkSetStartFrameExecute(object sender, ExecutedRoutedEventArgs e) {
            var item = (FeatureDetection.FeatureDetector.Trajection_Part)this.PosList.SelectedItem;
            if(item == null) return;
            startFrame = item.Num;
        }

        public static readonly ICommand OkSetEndFrame = new RoutedCommand("OkSetEndFrame", typeof(MainWindow));
        private void OkSetEndFrameExecute(object sender, ExecutedRoutedEventArgs e) {
            var item = (FeatureDetection.FeatureDetector.Trajection_Part)this.PosList.SelectedItem;
            if(item == null) return;
            endFrame = item.Num;
        }

        public static readonly ICommand OkSaveTrajection = new RoutedCommand("OkSaveTrajection", typeof(MainWindow));
        private void OkSaveTrajectionExecute(object sender, ExecutedRoutedEventArgs e) {
            Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
            dlg.FileName = "*.csv";
            dlg.DefaultExt = "*.csv";
            dlg.Filter = "CSVドキュメント(*.csv)|*.csv";
            dlg.AddExtension = true;
            if(dlg.ShowDialog() == true) {
                //適切な入力がされた
                var items = this.positions.Where((item, index) => (index + 1 >= startFrame && index + 1 <= endFrame));

                using(var s = new StreamWriter(dlg.FileName)) {
                    int line = 1;
                    foreach(var item in items) {
                        s.WriteLine((line++) + "," + item.X + "," + item.Y + "," + item.Z);
                    }
                }
            }
        }
        private void OkSaveTrajectionCanExecute(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = endFrame >= startFrame;
        }

        public static readonly ICommand OkFeatureCalc = new RoutedCommand("OkFeatureCalc", typeof(MainWindow));
        private void OkFeatureCalcExecute(object sender, ExecutedRoutedEventArgs e) {
            if(this.positions.Count <= 0) {
                System.Windows.Forms.MessageBox.Show("軌跡データが入力されていません");
                return;
            }

            var temp = FeatureDetector.TrajectionToFeature(this.positions);
            features.Clear();
            foreach(var item in temp) {
                features.Add(item);
            }
            this.drawer.Invalidate();
            System.Windows.Forms.MessageBox.Show("抽出は完了しました");
        }

        public static readonly ICommand OkSaveFeatures = new RoutedCommand("OkSaveFeatures", typeof(MainWindow));
        private void OkSaveFeaturesExecute(object sender, ExecutedRoutedEventArgs e) {
            if(this.features.Count <= 0) {
                System.Windows.Forms.MessageBox.Show("特徴データが抽出されていません");
                return;
            }

            Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
            dlg.FileName = "*.csv";
            dlg.DefaultExt = "*.csv";
            dlg.Filter = "CSVドキュメント(*.csv)|*.csv";
            dlg.AddExtension = true;
            if(dlg.ShowDialog() == true) {
                //適切な入力がされた
                using(var s = new StreamWriter(dlg.FileName)) {
                    foreach(var item in features) {
                        s.WriteLine(item.Num + "," + item.X + "," + item.Y + "," + item.Z);
                    }
                }
            }
        }

        #endregion
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow() {
            InitializeComponent();
            this.DataContext = this;

            //座標リスト初期化
            this.positions = new ObservableCollection<FeatureDetection.FeatureDetector.Trajection_Part>();
            this.features = new ObservableCollection<FeatureDetection.FeatureDetector.Trajection_Part>();

            //取り込みボタン初期化
            this._buttonInputText = "取り込み";
            this._okInput = new DelegateCommand {
                ExecuteHandler = OkInputExecute,
                CanExecuteHandler = OkInputCanExecute
            };

            //初期値設定
            this._startFrame = double.NaN;
            this._endFrame = double.NaN;

            //ビューア用画像生成
            this.drawingGroup = new DrawingGroup();
            this._imageSource = new DrawingImage(this.drawingGroup);

            this.drawer = new Drawer(drawingGroup, () => {

                drawingGroup.Dispatcher.Invoke(() => {
                    //ビューア描画ロジック
                    using(DrawingContext dc = drawingGroup.Open()) {
                        dc.DrawRectangle(Brushes.Gray, null, new Rect(0, 0, 640, 480));
                        FeatureDetection.FeatureDetector.Trajection_Part pos_old = null;
                        foreach(var pos in this.positions) {
                            dc.DrawEllipse(Brushes.Red, null, new Point(pos.X, pos.Y), 3, 3);
                            if(pos_old == null) {
                                pos_old = pos;
                                continue;
                            } else {
                                dc.DrawLine(new Pen(Brushes.Yellow, 1), pos_old.ToPoint(), pos.ToPoint());
                                pos_old = pos;
                            }
                        }
                        var item = PosList.SelectedItem;
                        if(item != null) {
                            var p = ((FeatureDetection.FeatureDetector.Trajection_Part)PosList.SelectedItem).ToPoint();
                            dc.DrawEllipse(Brushes.Red, null, new Point(p.X, p.Y), 8, 8);
                        }
                    }
                    //drawingGroup.ClipGeometry = new RectangleGeometry(new Rect(0, 0, 640, 480));
                });
            });

            //テスト描画
            using(DrawingContext dc = drawingGroup.Open()) {
                dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, 640, 480));
                dc.DrawEllipse(Brushes.Blue, null, new Point(200, 200), 75, 75);
            }
        }

        /// <summary>
        /// 選択要素の変更による再描画
        /// </summary>
        private void PosList_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            drawer.Invalidate();
        }
    }
}
